/**
 * <copyright>
 * </copyright>
 *
 * $Id$
 */
package Wires.impl;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.emf.ecore.util.EObjectContainmentEList;
import org.eclipse.emf.ecore.util.InternalEList;

import Wires.ActualParameter;
import Wires.BasicData;
import Wires.CompositeTransformation;
import Wires.CompositeTransformationType;
import Wires.ConnectableElement;
import Wires.DataFlow;
import Wires.FormalParameter;
import Wires.InputActualParameter;
import Wires.InputFormalParameter;
import Wires.Library;
import Wires.Model;
import Wires.Transformation;
import Wires.Type;
import Wires.TypedElement;
import Wires.WiresElement;
import Wires.WiresPackage;
import Wires.WiresSpecification;
import Wires.exep.InitialModelsNotFoundException;

/**
 * <!-- begin-user-doc -->
 * An implementation of the model object '<em><b>Specification</b></em>'.
 * <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link Wires.impl.WiresSpecificationImpl#getEls <em>Els</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public class WiresSpecificationImpl extends EObjectImpl implements WiresSpecification {
	/***************************************************/
	/***************** Modifications *******************/		

	protected static void execute(List<ConnectableElement> els, ConnectableElement initialIncoming) throws Exception{		
		for (ConnectableElement element : els){
			// Execute the elements with the initial incoming
			if (initialIncoming != null){				
				((ConnectableElementImpl) element).execute(initialIncoming);
			}

			// Find the element target elements
			List<ConnectableElement> targetElements = getTargetElements((ConnectableElement) element);
			for (ConnectableElement targetElement : targetElements){
				// Execute each target element
				((ConnectableElementImpl) targetElement).execute(element);				
				if (!(targetElement instanceof InputActualParameter)){
					// Find the next level and call recursively
					List<ConnectableElement> targetOutgoingEls = getTargetElements(targetElement);
					WiresSpecificationImpl.execute(targetOutgoingEls,targetElement);										
				}
			}
		}
	}

	// Returns the list of outgoing elements
	private static List<ConnectableElement> getTargetElements(ConnectableElement element){
		List<DataFlow> outgoing = ((ConnectableElement)element).getOutgoing();
		List<ConnectableElement> targets = new LinkedList<ConnectableElement>();
		for (DataFlow df : outgoing){
			targets.add(df.getTarget());
		}
		return targets;
	}

	// Returns the models which have not incoming data flow
	private List<ConnectableElement> getInitialModels(){
		List<ConnectableElement> res = new LinkedList<ConnectableElement>();
		for (WiresElement el : this.els){
			if (el instanceof Model && ( ((Model) el).getIncoming() == null || ((Model) el).getIncoming().isEmpty() ) ){
				res.add((Model)el);
			}			
		}
		return res;
	}

	// Initialize the active attribute of transformations.
	private void initializeActive(EList<WiresElement> els){	
		for (WiresElement el : els){					
			if (el instanceof Transformation && ((Transformation)el).getControlNode() != null){
				((TransformationImpl)el).setActive(false);	
			}else if (el instanceof CompositeTransformationType){
				initializeActive(((CompositeTransformationType)el).getEls());
			}						
		}		
	}
	
	/*
	 * Initialize the path property of the elements (els):
	 * If the diagram will be executed directly form the diagram editor,
	 * inside your own workspace, all the paths are relative to the workspace
	 * (standalone is false).
	 * Else if the diagram will be executed as part of a plug-in using the WiresLauncher
	 * interface, parameters paths are relative to the user WS and the paths of the elements
	 * inside the composites are taken as plug-in resources.
	 */
	private void initializePaths(boolean standalone, List<WiresElement> els){
		List<WiresElement> allEls = this.getAllElements(els);
		// Initialize path property of the elements			
		String innerElsPath = "";
		if (standalone){
			innerElsPath = WiresLauncher.RESOURCES_LOCATION;
		}else{
			innerElsPath = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString();
		}
		setElementsPaths(standalone, innerElsPath, allEls);
	}

	private void setElementsPaths(boolean standalone, String innerElsPath, List<WiresElement> allEls) {
		LinkedList<WiresElement> alreadyDone = new LinkedList<WiresElement>();
		for (WiresElement el : allEls){
//			boolean contains = false;
//			for (WiresElement adEl : alreadyDone){
//				if (el instanceof ConnectableElement && 
//						adEl instanceof ConnectableElement &&
//						((ConnectableElement)el).getName().equals(((ConnectableElement)adEl).getName()) &&
//						el.getClass().equals(adEl.getClass())){
//					contains = true;
//				}
//				
//				if (el instanceof Library && 
//						adEl instanceof Library &&
//						((Library)el).getName().equals(((Library)adEl).getName())){
//					contains = true;
//				}
//			}
			if (!alreadyDone.contains(el)){
				if (el instanceof Type && ((Type)el).getPath() != null
						&& !((Type)el).getPath().equals("")){				
					if (el instanceof FormalParameter){					
						((FormalParameter)el).setPath(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString()
								+ ((FormalParameter)el).getPath());					
					}else{
						String newPath = "";
						if (standalone){
							// Remove the project name from the original path					
							newPath = ((Type)el).getPath();
							newPath = newPath.substring(newPath.indexOf("\\") + 1);
							newPath = newPath.substring(newPath.indexOf("\\") + 1);
						}else{
							newPath = ((Type)el).getPath();
						}

						((Type)el).setPath(innerElsPath + newPath);
					}
				}else if (el instanceof TypedElement){
					if (el instanceof Model && ((Model)el).getPath() != null
							&& !((Model)el).getPath().equals("")){
						((Model)el).setPath(innerElsPath + ((Model)el).getPath());
					}else if (el instanceof BasicData && ((BasicData)el).getPath() != null
							&& !((BasicData)el).getPath().equals("")){
						((BasicData)el).setPath(innerElsPath + ((BasicData)el).getPath());
					}
				}else if (el instanceof Library){
					((Library)el).setPath(innerElsPath + ((Library)el).getPath());
				}
			}
			
			alreadyDone.add(el);
		}
	}
	
	// Returns all the elements in els, including types.
	private List<WiresElement> getAllElements(List<WiresElement> els){
		List<WiresElement> allEls = new LinkedList<WiresElement>();
		for (WiresElement el : els){
			allEls.add(el);
			
			// Also add its type
			if (el instanceof TypedElement){
				allEls.add(((TypedElement) el).getType());
			}
			
			// Also add its actual and formal parameters (and metamodel)
			if (el instanceof Transformation){
				for (ActualParameter p : ((Transformation) el).getInParams()){
					allEls.add(p);
					allEls.add(p.getType());
					
					if (p.getType()!= null){ 
						if (!allEls.contains(((FormalParameter)p.getType()).getTypeEl()))
							allEls.add(((FormalParameter)p.getType()).getTypeEl());
					}
				}
				for (ActualParameter p : ((Transformation) el).getOutParams()){
					allEls.add(p);
					allEls.add(p.getType());
					if (p.getType()!=null){
						if (!allEls.contains(((FormalParameter)p.getType()).getTypeEl()))
							allEls.add(((FormalParameter)p.getType()).getTypeEl());
					}
				}
				if (el instanceof CompositeTransformation){
					allEls.addAll(getAllElements(((CompositeTransformationType) ((CompositeTransformation)el).getType()).getEls()));
				}
			}	
			
			// Also add its formal parameters and all the elements it contains
			if (el instanceof CompositeTransformationType){
				for (FormalParameter p : ((CompositeTransformationType) el).getInParams()){
					allEls.add(p);
					allEls.add(p.getTypeEl());
				}
				for (FormalParameter p : ((CompositeTransformationType) el).getOutParams()){
					allEls.add(p);
					allEls.add(p.getTypeEl());
				}				
				
				allEls.addAll(getAllElements(((CompositeTransformationType) el).getEls()));
			}
		}
						
		return allEls;		
	}
	
	// Returns the list of all the CompositesTransformationType
	private List<CompositeTransformationType> getComposites(){
		List<CompositeTransformationType> res = new LinkedList<CompositeTransformationType>();
		for (WiresElement el : this.els){
			if (el instanceof CompositeTransformationType)
				res.add((CompositeTransformationType) el);
		}
		return res;
	}		

	// Runs the model starting by the initial Models
	public void runExecution() throws Exception{
		initializeActive(this.els);
		initializePaths(false,this.els);
		List<ConnectableElement> initialModels = this.getInitialModels();
		if (initialModels == null || initialModels.isEmpty())
			throw new InitialModelsNotFoundException();
		WiresSpecificationImpl.execute(initialModels, null);
	}
	
	// Run the composite stand alone (using the original path)
	public void runExcecutionStandAlone(Map<String, String> args) throws Exception{
		List<CompositeTransformationType> composites = this.getComposites();
		if (composites.isEmpty() || args.isEmpty()){
			this.runExecution();
		}else{
			for (CompositeTransformationType c : composites){
				((CompositeTransformationTypeImpl)c).instanciateArgumentsStandAlone(args);
				initializeActive(c.getEls());
				List<ConnectableElement> list = new LinkedList<ConnectableElement>();
				for (InputFormalParameter ip : c.getInParams()){
					list.add((ConnectableElement)ip);
				}				
				execute(list, null);
			}
		}
	}

	// Runs the ComopsitesTransformationType(s) of the model
	public void runExecution(Map<String,String> args) throws Exception{
		List<CompositeTransformationType> composites = this.getComposites();
		if (composites.isEmpty() || args.isEmpty()){
			this.runExecution();
		}else{
			for (CompositeTransformationType c : composites){
				((CompositeTransformationTypeImpl)c).instanciateArguments(args,true);
				initializePaths(true, c.getEls());
				initializeActive(c.getEls());
				List<ConnectableElement> list = new LinkedList<ConnectableElement>();
				for (InputFormalParameter ip : c.getInParams()){
					list.add((ConnectableElement)ip);
				}				
				execute(list, null);
			}
		}
	}

	// Returns the first Model which name matches with modelName parameter
	public Model getModelByName(String modelName){		
		Model m = null;
		for (WiresElement el : this.getEls()){
			if (el instanceof Model && ((Model)el).getName().equals(modelName)){
				m = (Model)el;
				return m;
			}				
		}		
		return m;
	}

	/**************** Modifications End ****************/
	/***************************************************/



	/**
	 * The cached value of the '{@link #getEls() <em>Els</em>}' containment reference list.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @see #getEls()
	 * @generated
	 * @ordered
	 */
	protected EList<WiresElement> els;

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected WiresSpecificationImpl() {
		super();
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return WiresPackage.Literals.WIRES_SPECIFICATION;
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	public EList<WiresElement> getEls() {
		if (els == null) {
			els = new EObjectContainmentEList<WiresElement>(WiresElement.class, this, WiresPackage.WIRES_SPECIFICATION__ELS);
		}
		return els;
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs) {
		switch (featureID) {
			case WiresPackage.WIRES_SPECIFICATION__ELS:
				return ((InternalEList<?>)getEls()).basicRemove(otherEnd, msgs);
		}
		return super.eInverseRemove(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case WiresPackage.WIRES_SPECIFICATION__ELS:
				return getEls();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case WiresPackage.WIRES_SPECIFICATION__ELS:
				getEls().clear();
				getEls().addAll((Collection<? extends WiresElement>)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case WiresPackage.WIRES_SPECIFICATION__ELS:
				getEls().clear();
				return;
		}
		super.eUnset(featureID);
	}

	/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case WiresPackage.WIRES_SPECIFICATION__ELS:
				return els != null && !els.isEmpty();
		}
		return super.eIsSet(featureID);
	}

} //WiresSpecificationImpl
